package agent;

import java.util.NoSuchElementException;
import java.util.Vector;

import requestEnvelopes.server.Actuate;
import requestEnvelopes.server.Create;
import requestEnvelopes.server.Flash;
import requestEnvelopes.server.GetLocalAgentsID;
import requestEnvelopes.server.GetMyIEEEAddress;
import requestEnvelopes.server.GetNeighbors;
import requestEnvelopes.server.GetRemoteAgentsID;
import requestEnvelopes.server.Input;
import requestEnvelopes.server.Migrate;
import requestEnvelopes.server.ResetTimer;
import requestEnvelopes.server.Send;
import requestEnvelopes.server.Sense;
import requestEnvelopes.server.SetTimer;
import requestEnvelopes.server.StartAgent;
import requestEnvelopes.server.TerminateAgent;

import com.CharNotValidException;
import com.Event;
import com.LedsManager;
import com.ParamsLabel;
import com.sun.spot.interisolate.InterIsolateServer;
import com.sun.spot.interisolate.ReplyEnvelope;
import com.sun.spot.interisolate.RequestSender;
import com.sun.spot.util.IEEEAddress;
import com.sun.squawk.io.mailboxes.NoSuchMailboxException;

/**
 * Agent is an abstract class that must be inherited from the developed Agent. The developed Agent is an Isolate,
 * in this way the Agent can migrate from a node to another. The developed class must have a main method where
 * it is instantiated.
 * For example if we have a class called MyMobileAgent, the code is:
 * 
 * <p >
 * public class MyMobileAgent extends Agent {<br>
 *	<br>
 *	public MyMobileAgent(String id, String executionAgentAddress) throws NoSuchMailboxException {<br>
 *		super(id, executionAgentAddress);<br>
 *		
 *		MyMobileAgentPlane tp = new MyMobileAgentPlane(this);<br>
 *		multiplaneStateMachine.addElement(tp);<br>
 *	}<br>
 *  <br>
 *	public static void main (String [] args) {<br>
 *		
 *		try<br>
 *	{<br>
 *		MyMobileAgent myMobileAgent = new MyMobileAgent(args[0], args[1]);<br>
 *			<br>
			while(!dataCollector.isTerminated()){				<br>
				<br>
				dataCollector.waitForEvents();<br>
				<br>
				dataCollector.run();<br>
			}<br>
			<br>
		}<br>
		catch(Exception e)<br>
		{<br>
			LedsManager.error();<br>
			e.printStackTrace();<br>
		}<br>
	}<br>
 * The while cycle is needed to wait incoming events. When an event arrives the run method starts and dispatches 
 * the events to every plane of the Agent
 *  
 * @author Francesco Aiello, Antonio Guerrieri, Raffaele Gravina
 */
public abstract class Agent{
	
	
	/**
	 * generic set of global variables
	 */
	protected Object globalVariables;
	
	
	/**
	 * Agent's ID
	 */
	protected String id;
	
	/**
	 * Multiplane State Machine
	 */
	protected Vector multiplaneStateMachine;
	
	/**
	 * Event Queue
	 */
	protected Vector eventQueue;
	
	/**
	 * Agent Server Address
	 */
	protected String agentServerAddress;
	
	/**
	 * The RequestSender object for Agent's requests to Agent Server  
	 */
	protected RequestSender agentServer;
	
	/**
	 * Shows if a migration has been requested by the Agent
	 */
	private boolean migrationRequested;
	
	/**
	 * Destination Node ID for the migration procedure 
	 */
	private String migrationDestNodeID;
	
	/**
	 * Flag used to know if Agent execution is terminated
	 */
	private boolean terminated;
	
	/**
	 * Constructor must create some Planes and put them into the 'multiplaneStateMachine' Vector
	 * 
	 * @param id Agent's ID
	 * @param executionAgentAddress Agent's Server address (used for the communication between the Agent and the Agent Server
	 * @throws NoSuchMailboxException
	 */
	public Agent (String id, String executionAgentAddress) throws NoSuchMailboxException {
				
		this.id 					= id;
		this.agentServerAddress		= executionAgentAddress;
		this.multiplaneStateMachine = new Vector();
		this.eventQueue				= new Vector();
		this.agentServer 			= RequestSender.lookup(this.agentServerAddress);
		migrationRequested          = false;
		migrationDestNodeID         = "";
	
		InterIsolateServer.run(id, this);
		
		terminated = false;
	}
	
	/**
	 * Returns the Agent's ID
	 * 
	 * @return the Agent's ID
	 */
	public String getId() {
		return id;
	}
	
	/**
	 * Returns the Global Variables Object
	 * 
	 * @return the globalVariables Object
	 */
	public Object getGlobalVariables() {
		return globalVariables;
	}
	
	/**
	 * Setter of the property <tt>globalVariables</tt>
	 * 
	 * @param globalVariables
	 *            The globalVariables to set
	 */
	public void setGlobalVariables(Object globalVariables) {

		this.globalVariables = globalVariables;
	
	}
	
	
	/**
	 * Getter of the property <tt>multiplaneStateMachine</tt>
	 * 
	 * @return Returns the multiplaneStateMachine.
	 */
	public Vector getMultiplaStateMachine() {
	
		return multiplaneStateMachine;
	
	}
	
	/**
	 * Setter of the property <tt>multiplaneStateMachine</tt>
	 * 
	 * @param multiplaneStateMachine
	 *            The multiplaneStateMachine to set.
	 */
	public void setMultiplaStateMachine(Vector multiplaneStateMachine) {
	
		this.multiplaneStateMachine = multiplaneStateMachine;
	
	}
	
	/**
	 * Receives the events to put into the Event Queue
	 * @param event the event object
	 * @return if the operation has been successfull or not
	 */
	public synchronized boolean receiveEvent(Event event) {
		
		if(!migrationRequested){ 	
			this.eventQueue.addElement(event);
			if (event.getName() == Event.AGN_TERMINATED) {
				
				this.setTerminated();
			}
			this.notifyAll();
		}
		else if(migrationRequested && event.getName() == Event.MGR_EXECUTED){
			this.migrationExecuted();
			this.eventQueue.addElement(event);
			this.notifyAll();
		}		
		else if(migrationRequested && event.getName() == Event.AGN_TERMINATED){
			this.eventQueue.addElement(event);			
			this.setTerminated();			
			this.notifyAll();
		}	
		return true;
	}
	
	/**
	 * This is called by "receiveEvent" method when the migration has been executed 
	 */
	private void migrationExecuted() {
		try {
			this.agentServer = RequestSender.lookup(this.agentServerAddress);
		} catch (NoSuchMailboxException e) {
			LedsManager.error();
			e.printStackTrace();			
		}
		
		migrationRequested          = false;
		migrationDestNodeID         = "";
		
	}
	
	/**
	 * Getter of the property agentServerAddress
	 * 
	 * @return Returns the agentServerAddress.
	 */
	public String getAgentServerAddress() {
	
		return agentServerAddress;
	
	}
	
	/**
	 * Setter of the property agentServerAddress
	 * 
	 * @param agentServerAddress
	 *            The agentServerAddress to set.
	 */
	public void setAgentServerAddress(String agentServerAddress) {
	
		this.agentServerAddress = agentServerAddress;
	
	}
	
	/**
	 * Fetch the events and dispatch them to the Multiplane State Machine.
	 * It uses a FIFO policy
	 */
	public void run(){
		
		if(!migrationRequested){
			boolean noEvents = false;
			Event eventTemp = null;
						
			while(!noEvents){
				try {
					eventTemp = (Event)this.eventQueue.firstElement();
					for(int j=0; j<this.multiplaneStateMachine.size(); j++){						
						((Plane)this.multiplaneStateMachine.elementAt(j)).eventHandler(eventTemp);
					}
					this.eventQueue.removeElementAt(0);
				} catch (NoSuchElementException e) {
					noEvents = true;					
				} catch (Exception e) {	
					LedsManager.error();
				}
			} // while
			if(this.migrationRequested) callForMigration();
			
		}
	}
	
	/**
	 * Called by the main method to wait incoming events
	 */
	public synchronized void waitForEvents() {
		while (this.eventQueue.size() == 0) {
			try {
				wait();				
			}
			catch (InterruptedException e) {
				LedsManager.error();
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Sends to the AgentServer the request for migration.
	 */
	public void callForMigration(){
		
		ReplyEnvelope reply = this.agentServer.send(
				new Migrate(this.id, this.migrationDestNodeID));
		
		reply.checkForRuntimeException();
	}
	
	/**
	 * Set the migrationRequested variable to "true" and the address of the migrationDestNode variable
	 * @param nodeIdDest Destination address 
	 * @return true if the request is accepted otherwise false if a migration is already requested 
	 */
	public boolean askForMigration(String nodeIdDest){
		if (migrationRequested) return false;
		else{
			migrationRequested = true;
			migrationDestNodeID = nodeIdDest;
		}
		return true;
	}
	
	/**
	 * Returns the migrationRequested value 
	 * @return migrationRequested value
	 */
	public boolean isMigrationRequested(){
		return migrationRequested;
	}

	/**
	 * Returns the terminated value
	 * @return terminated value
	 */
	public boolean isTerminated() {
		return this.terminated;
	}

	/**
	 * Setter of the property terminated
	 */
	public void setTerminated() {
		this.terminated = true;
	}

	/**
	 * Invokes the Agent Server's send method 
	 * @param sourceMA ID of the source MA
	 * @param targetMA ID of the target MA
	 * @param message Message Event to send
	 * @param local flag shows if the message is only for local agent or not
	 */
	public void send(String sourceMA, String targetMA, Event message, boolean local) {
		ReplyEnvelope reply = this.agentServer.send(
				new Send(sourceMA, targetMA,  message,  local));		
		reply.checkForRuntimeException();
	}
	
	/**
	 * Asks to Agent Server the list of Remote Agents
	 * @return the list of Remote Agents
	 */
	public Vector getRemoteAgentsID() {
		ReplyEnvelope reply = this.agentServer.send(
				new GetRemoteAgentsID());		
		reply.checkForRuntimeException();
		return (Vector)reply.getContents();
	}
	
	/**
	 * Asks to Agent Server the list of Local Agents
	 * @return the list of Local Agents
	 */
	public Vector getLocalAgentsID() {
		ReplyEnvelope reply = this.agentServer.send(
				new GetLocalAgentsID());		
		reply.checkForRuntimeException();
		return (Vector)reply.getContents();
	}
	
	/**
	 * Asks to Agent Server the list of the near nodes
	 * @return the list of the near nodes
	 */
	public Vector getNeighbors() {
		ReplyEnvelope reply = this.agentServer.send(
				new GetNeighbors());		
		reply.checkForRuntimeException();
		return (Vector)reply.getContents();
	}
	
	/**
	 * Asks to Agent Server the Start Event
	 */
	public void startAgent() {
		ReplyEnvelope reply = this.agentServer.send(
				new StartAgent(this.id));		
		reply.checkForRuntimeException();
	}
	
	/**
	 * Asks to Agent Server the Terminate Event
	 */
	public void terminateAgent() {
		ReplyEnvelope reply = this.agentServer.send(
				new TerminateAgent(this.id));		
		reply.checkForRuntimeException();
	}

	/**
	 * Asks to Agent Server the instantiation of a Mobile Agent
	 * @param agent	class of the Mobile Agent
	 * @param params parameters for Mobile Agent
	 * @param nodeID ID of the node where the Mobile Agent has to be instantiated 
	 * @return if instantiation has been successful or not
	 */
	public boolean create(String agent, String [] params, String nodeID) {
		ReplyEnvelope reply = this.agentServer.send(
				new Create(this.id, agent, params, nodeID));		
		reply.checkForRuntimeException();
		return ((Boolean)reply.getContents()).booleanValue();
	}
	
	/**
	 * Asks to Agent Server the instantiation of a Mobile Agent. Here the Agent's ID is specified too
	 * @param agentID ID of the Mobile Agent to instantiate
	 * @param agent	class of the Mobile Agent
	 * @param params parameters for Mobile Agent
	 * @param nodeID ID of the node where the Mobile Agent has to be instantiated 
	 * @return if instantiation has been successful or not
	 */	
	public boolean create(String agentID, String agent, String [] params, String nodeID) {
		ReplyEnvelope reply = this.agentServer.send(
				new Create(agentID, this.id, agent, params, nodeID));		
		reply.checkForRuntimeException();	
		return ((Boolean)reply.getContents()).booleanValue();
	}
	
	/**
	 * Gets the MAC address of the local node
	 * @return IEEEAddress of the local node
	 */
	public IEEEAddress getMyIEEEAddress() {
		ReplyEnvelope reply = this.agentServer.send(
				new GetMyIEEEAddress());		
		reply.checkForRuntimeException();
		return (IEEEAddress)reply.getContents();
	}
	
	/**
	 * Set a new timer
	 * @param periodic if it is periodic or not
	 * @param timeout timeout of the timer
	 * @param backEvent event to send to the Mobile Agent at the timeout of the timer
	 * @return Timer's ID
	 */
	public String setTimer(boolean periodic, long timeout, Event backEvent) {
		ReplyEnvelope reply = this.agentServer.send(
				new SetTimer(this.id, periodic, timeout, backEvent));	
		reply.checkForRuntimeException();
		return (String)reply.getContents();
	}

	/**
	 * Terminates a timer
	 * @param timerID Timer's ID
	 */
	public void resetTimer(String timerID) {
		ReplyEnvelope reply = this.agentServer.send(
				new ResetTimer(this.id, timerID));	
		reply.checkForRuntimeException();
	}
	
	/**
	 * Access to sensing resources
	 * @param backEvent Event with sensed data 
	 */
	public void sense(Event backEvent) {
		ReplyEnvelope reply = this.agentServer.send(
				new Sense(this.id, backEvent));	
		reply.checkForRuntimeException();
	}
	
	/**
	 * Access to actuate a resource
	 * @param backEvent Event with data from actuated resource  
	 */
	public void actuate(Event backEvent) {
		ReplyEnvelope reply = this.agentServer.send(
				new Actuate(this.id, backEvent));	
		reply.checkForRuntimeException();
	}
	
	/**
	 * Access to flash memory to save a byte array
	 * @param backEvent Event with data from actuated resource  
	 */
	public void flash(Event backEvent, byte [] array_byte) {
		String string_dati = array_byte.length + "|";
		
		for (int i = 0; i < array_byte.length; i++) {
			string_dati += array_byte[i] + "|";
		}
		try {
			backEvent.setParam(ParamsLabel.FLS_DATA, string_dati);
		}
		catch (CharNotValidException e) {
			e.printStackTrace();
		}
		ReplyEnvelope reply = this.agentServer.send(
				new Flash(this.id, backEvent));	
		reply.checkForRuntimeException();
	}	
	
	/**
	 * Handle node IO resources
	 * @param backEvent Event with data from IO resource 
	 */
	public void input(Event backEvent) {
		ReplyEnvelope reply = this.agentServer.send(
				new Input(this.id, backEvent));	
		reply.checkForRuntimeException();
	}

	/*
	 * main method must instantiate an agent, giving to it two required parameters (ID, agentServer),
	 * then call run method.
	 * public static void main()
	 */
	
	
}
